using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

using EntModApp.EntServices;
using EntModApp.EntServices.Data;

namespace DemoApp.DataAccess 
{
  public partial class UserQueryableRepository : 
    DbContextQueryableRepository<DemoAppModelDbContext, User>,
    IQueryableUserRepository
  {
    public UserQueryableRepository(DemoAppModelDbContext context)
      : this(context, false)
    {
    }

    public UserQueryableRepository()
      : this(new DemoAppModelDbContext(), true)
    {
    }

    protected UserQueryableRepository(DemoAppModelDbContext context, bool ownContext)
      : base(context, context.Users, ownContext)
    {
    }
  }

  public partial class UserDataRepository : 
    DbContextDataRepository<DemoAppModelDbContext, User, UserLoadSettings>,
    IUserRepository
  {
    public UserDataRepository(DemoAppModelDbContext context)
      : this(context, false)
    {
    }

    public UserDataRepository()
      : this(new DemoAppModelDbContext(), true)
    {
    }

    protected UserDataRepository(DemoAppModelDbContext context, bool ownContext)
      : base(context, context.Users, ownContext)
    {
    }

    public override IEnumerable<User> RetrieveAll(out int total, bool retrieveTotal)
    {
      var query = new UserLoadSettings
      {
        Paging = new PagingSettings { ComputeTotal = retrieveTotal }
      };
      return Retrieve(query, out total);
    }

    public override IEnumerable<User> Retrieve(UserLoadSettings query, out int total)
    {
      var dbQuery = DataSet.ApplyUserLoadSettings(query, out total);
      return dbQuery.ToList();
    }

    public override int Count()
    {
      return DataSet.Count();
    }

    public override int Count(UserLoadSettings query)
    {
      var dbQuery = DataSet.ApplyUserFilterExpression(query.Filter);
      return dbQuery.Count();
    }
  }

  public static class UserQueryableRepositoryExtensions
  {
    public static IQueryable<User> ApplyUserLoadSettings(this IQueryable<User> query, UserLoadSettings loadSettings, out int total)
    {
      if (loadSettings == null)
        loadSettings = new UserLoadSettings();

      query = query.ApplyUserInclusionSettings(loadSettings.Inclusion);

      query = query.ApplyUserFilterExpression(loadSettings.Filter);

      query = query.ApplyUserSortingSettings(loadSettings.Sorting);

      query = query.ApplyPagingSettings(loadSettings.Paging, out total);

      query = query.ApplyUserSelectionSettings(loadSettings.Selection);

      return query;
    }

    public static IEnumerable<string> GetDirectUserInclusionPaths(this UserInclusionSettings inclusionSettings)
    {
      if (inclusionSettings.IncludeRoles) yield return "Roles";
      if (inclusionSettings.IncludeGroups) yield return "Groups";
      if (inclusionSettings.IncludeCreatedSubjects) yield return "CreatedSubjects";
      if (inclusionSettings.IncludeEditedSubjects) yield return "EditedSubjects";

      yield break;
    }

    public static IEnumerable<string> GetUserInclusionPaths(this UserInclusionSettings inclusionSettings)
    {
      foreach (var path in inclusionSettings.GetDirectUserInclusionPaths())
        yield return path;

      if (inclusionSettings.IncludeRoles)
        foreach (var referencePath in inclusionSettings.IncludedRolesSettings.Inclusion.GetRoleInclusionPaths())
          yield return "Roles." + referencePath;
      if (inclusionSettings.IncludeGroups)
        foreach (var referencePath in inclusionSettings.IncludedGroupsSettings.Inclusion.GetGroupInclusionPaths())
          yield return "Groups." + referencePath;
      if (inclusionSettings.IncludeCreatedSubjects)
        foreach (var referencePath in inclusionSettings.IncludedCreatedSubjectsSettings.Inclusion.GetSubjectInclusionPaths())
          yield return "CreatedSubjects." + referencePath;
      if (inclusionSettings.IncludeEditedSubjects)
        foreach (var referencePath in inclusionSettings.IncludedEditedSubjectsSettings.Inclusion.GetSubjectInclusionPaths())
          yield return "EditedSubjects." + referencePath;

      yield break;
    }

    public static IQueryable<User> ApplyUserInclusionSettings(this IQueryable<User> query, UserInclusionSettings inclusionSettings)
    {
      foreach (var path in inclusionSettings.GetUserInclusionPaths())
        query = query.Include(path);

      return query;
    }

    public static IQueryable<User> ApplyUserFilterExpression(this IQueryable<User> query, UserFilterExpression filterExpression)
    {
      if (filterExpression.ByKey != null)
        query = query.Where(o => o.UserId == filterExpression.ByKey.UserId);

      var parameter = Expression.Parameter(typeof(User), "e");
      if (filterExpression.UserIdFilter != null)
        query = query.Where(filterExpression.UserIdFilter.CreateExpressionBody<User, Guid>(UserPropertyNames.UserId, parameter).ToLambda<User>(parameter));
      if (filterExpression.UserNameFilter != null)
        query = query.Where(filterExpression.UserNameFilter.CreateExpressionBody<User, string>(UserPropertyNames.UserName, parameter).ToLambda<User>(parameter));
      if (filterExpression.PasswordFilter != null)
        query = query.Where(filterExpression.PasswordFilter.CreateExpressionBody<User, string>(UserPropertyNames.Password, parameter).ToLambda<User>(parameter));

      if (filterExpression.Skip.HasValue)
        query = query.Skip(filterExpression.Skip.Value);
      if (filterExpression.Take.HasValue)
        query = query.Take(filterExpression.Take.Value);

      return query;
    }

    public static IQueryable<User> ApplyUserSortingSettings(this IQueryable<User> query, UserSortingSettings sortingSettings)
    {
      return query;
    }

    public static IQueryable<User> ApplyUserSelectionSettings(this IQueryable<User> query, UserSelectionSettings selectionSettings)
    {
      return query;
    }

  }
}
